%%%-------------------------------------------------------------------
%%% File    : svc_glog.erl
%%% Author  :  <vjache>
%%% Description : 
%%%
%%% Created : 12 Jun 2009 by  <vjache>
%%%-------------------------------------------------------------------
-module(svc_glog).

-behaviour(gen_event).
%% API
-export([
		 start_link/0,
		 pf_any/1, 
		 pf_err/1,
		 pf_info/1,
		 pf_warn/1,
		 add_disk_log_h/2,
		 add_proc_cons_h/2,
		 capture2tty/1,
		 capture2file/2,
		 last2tty/1,
		 format_entry/1,
		 pf_level/2]).

-include("../include/glog.hrl").

%% gen_event callbacks
-export([init/1, handle_event/2, handle_call/2, 
	 handle_info/2, terminate/2, code_change/3]).

-record(state_disk_log, {arg_list, name, pred_fun=fun pf_any/1}).
-record(state_dispatcher, {evt_man_ref, pred_fun=fun pf_any/1}).
-record(state_proc_cons, {pid, pred_fun=fun pf_any/1}).

-define(SERVER,?MODULE).

%%====================================================================
%% gen_event callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: start_link() -> {ok,Pid} | {error,Error} 
%% Description: Creates an event manager.
%%--------------------------------------------------------------------
start_link() ->
    R=gen_event:start_link({global, ?SERVER}),
	add_default_h(),
	R. 

%%--------------------------------------------------------------------
%% Function: add_handler() -> ok | {'EXIT',Reason} | term()
%% Description: Adds an event handler
%%--------------------------------------------------------------------
add_default_h() ->
    add_disk_log_h([{name,?LOG_NAME},{file,"./global.log"}],fun pf_any/1).
add_disk_log_h(ArgL,PredFun) ->
    gen_event:add_handler({global, ?SERVER}, ?MODULE, Arg={disk_log,ArgL,PredFun}),
	Arg.

add_proc_cons_h(Pid,PredFun) when is_pid(Pid) andalso is_function(PredFun, 1)->
    gen_event:add_handler({global, ?SERVER}, ?MODULE, Arg={proc_cons,Pid,PredFun}),
	Arg.

pf_err(Entry) ->
	pf_level(Entry,err).
pf_warn(Entry) ->
	pf_level(Entry,warn).
pf_info(Entry) ->
	pf_level(Entry,info).

pf_level(_,all) ->
	true;
pf_level({_Event,{Level,_,_,_}},Level) ->
	true;
pf_level(_,_) ->
	false.

capture2tty(Level) when is_atom(Level)->
	capture2tty(fun(Entry)->
						pf_level(Entry,Level)
				end);
capture2tty(PredFun) when is_function(PredFun,1) ->
	LoopPid=spawn_link(fun()->
					   tty_loop()
			   end),
	add_proc_cons_h(LoopPid,PredFun),
	LoopPid.
tty_loop()->
	receive
		{captured,Event} ->
			io:format(lists:flatten(format_entry(Event))),
			tty_loop()
	end.

capture2file(Filename,Level) when is_atom(Level) ->
	capture2file(Filename,
				 fun(Entry)->
						 pf_level(Entry,Level)
				 end);
capture2file(Filename,PredFun) when is_function(PredFun,1) ->
	LoopPid=spawn_link(fun()->
							   case disk_log:open([{name,Filename},{file,Filename},{format,external}]) of
								   {ok,Filename} ->
									   Filename;
								   {repaired, Filename, {recovered, _Rec}, {badbytes, _Bad}} ->
									   Filename;
								   {error, Reason} ->
									   throw(Reason)
							   end,
							   file_loop(Filename)
					   end),
	add_proc_cons_h(LoopPid,PredFun),
	LoopPid.

file_loop(Filename)->
	receive
		{captured,Event} ->
			Txt=lists:flatten(format_entry(Event)),
			disk_log:balog(Filename, list_to_binary(Txt)),
			file_loop(Filename)
	end.

last2tty(N) when is_integer(N)->
	lists:foreach(
	  fun(Entry) ->
			  io:format(lists:flatten(format_entry(Entry)))
	  end,last2tty_loop(start,N,[[],[]])).
last2tty_loop(Cont,N,[P1,P0|_]) ->
	case disk_log:chunk(?LOG_NAME, Cont, N) of
		{error, Reason} ->
			throw(Reason);
		{Cont2,Terms} ->
			last2tty_loop(Cont2,N,[Terms,P1]);
		{Cont2, Terms, _Badbytes} ->
			last2tty_loop(Cont2,N,[Terms,P1]);
		eof ->
			P0++P1
	end.
%%====================================================================
%% gen_event callbacks
%%====================================================================
%%--------------------------------------------------------------------
%% Function: init(Args) -> {ok, State}
%% Description: Whenever a new event handler is added to an event manager,
%% this function is called to initialize the event handler.
%%--------------------------------------------------------------------
init({disk_log,ArgL,PredFun}) when is_list(ArgL),is_function(PredFun, 1) ->
	Name=open_log(ArgL),
    {ok, #state_disk_log{arg_list=ArgL,name=Name,pred_fun=PredFun}};
init({proc_cons,Pid,PredFun}=Arg) ->
	spawn_unreg_daemon(Pid,Arg),
    {ok, #state_proc_cons{pid=Pid,pred_fun=PredFun}};
init({dispatcher,PredFun}) when is_function(PredFun, 1) ->
	init({dispatcher,{global,?SERVER},PredFun});
init({dispatcher,EvtManRef,PredFun}) when is_function(PredFun, 1) ->
    {ok, #state_dispatcher{evt_man_ref=EvtManRef,pred_fun=PredFun}}.

spawn_unreg_daemon(PidToMonitor,Arg) when is_pid(PidToMonitor) ->
	spawn(fun()->
					   Ref=erlang:monitor(process, PidToMonitor),
					   receive
						   {'DOWN', Ref, process, PidToMonitor, _Info} ->
							   gen_event:delete_handler(
								 {global, ?SERVER}, ?MODULE, Arg)
					   end
			   end).


%%--------------------------------------------------------------------
%% Function:  
%% handle_event(Event, State) -> {ok, State} |
%%                               {swap_handler, Args1, State1, Mod2, Args2} |
%%                               remove_handler
%% Description:Whenever an event manager receives an event sent using
%% gen_event:notify/2 or gen_event:sync_notify/2, this function is called for
%% each installed event handler to handle the event. 
%%--------------------------------------------------------------------
handle_event(Entry, State) when is_record(State, state_proc_cons) ->
	PredFun=State#state_proc_cons.pred_fun,
	case PredFun(Entry) of
		true ->
			State#state_proc_cons.pid!{captured,Entry};
		false ->
			ok
	end,
	{ok, State};
handle_event(Event, State) when is_record(State, state_dispatcher) ->
	PredFun=State#state_dispatcher.pred_fun,
	case PredFun(Entry=to_entry(Event)) of
		true ->
			gen_event:notify(State#state_dispatcher.evt_man_ref, Entry);
		false ->
			ok
	end,
	{ok, State};
handle_event(Entry, State) when is_record(State, state_disk_log) ->
	PredFun=State#state_disk_log.pred_fun,
	case PredFun(Entry) of
		true ->
			case disk_log:alog(State#state_disk_log.name, Entry) of
				ok ->
					ok;
				{error,no_such_log} ->
					open_log(State#state_disk_log.arg_list),
					handle_event(Entry, State)
			end;
		false ->
			ok
	end,
	{ok, State}.

to_entry(Event) ->
	{Event,meta(Event)}.

meta({error, Gleader, {Pid, _Format, _Data}}) ->
	{err,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({error_report, Gleader, {Pid, std_error, _Report}}) ->
	{err,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({error_report, Gleader, {Pid, _Type, _Report}}) ->
	{err,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({warning_msg, Gleader, {Pid, _Format, _Data}}) ->
	{warn,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({warning_report, Gleader, {Pid, std_warning, _Report}}) ->
	{warn,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({warning_report, Gleader, {Pid, _Type, _Report}}) ->
	{warn,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({info_msg, Gleader, {Pid, _Format, _Data}}) ->
	{info,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({info_report, Gleader, {Pid, std_info, _Report}}) ->
	{info,now(),node(Gleader),pid2node(Gleader,Pid)};
meta({info_report, Gleader, {Pid, _Type, _Report}}) ->
	{info,now(),node(Gleader),pid2node(Gleader,Pid)}.

pid2node(Gleader,emulator) ->
	node(Gleader);
pid2node(_Gleader,Pid) ->
	node(Pid).

caption(Node,Pid,Ts,Level) ->
	%% 	2008-10-15T07:18:09.221Z
	%% 	{Year, Month, Day},{Hour, Minute, Second}
	{{Y, M, D},{H, Mn, S}}=calendar:now_to_local_time(Ts),
	[io_lib:format("~n#############################~n",
				   []),
	 io_lib:format("# Level : ~p ~n",
				   [Level]),
	 io_lib:format("# Time  : ~p-~p-~pT~p:~p:~p~n",
				   [Y, M, D, H, Mn, S]),
	 io_lib:format("# Node  : ~p~n",[Node]),
	 io_lib:format("# Pid   : ~p~n",[Pid])
	].
format_entry({
			  _Event={error, _Gleader, {Pid, Format, Data}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format(Format,Data),"\n"];
format_entry({
			  _Event={error_report, _Gleader, {Pid, std_error, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"];
format_entry({
			  _Event={error_report, _Gleader, {Pid, _Type, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"];
format_entry({
			  _Event={warning_msg, _Gleader, {Pid, Format, Data}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format(Format,Data),"\n"];
format_entry({
			  _Event={warning_report, _Gleader, {Pid, std_warning, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"];
format_entry({
			  _Event={warning_report, _Gleader, {Pid, _Type, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"];
format_entry({
			  _Event={info_msg, _Gleader, {Pid, Format, Data}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format(Format,Data),"\n"];
format_entry({
			  _Event={info_report, _Gleader, {Pid, std_info, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"];
format_entry({
			  _Event={info_report, _Gleader, {Pid, _Type, Report}},
			  _Meta={Level,Ts,_GlNode,Node}}) ->
	[caption(Node,Pid,Ts,Level),
	 io_lib:format("~p",[Report]),"\n"].



%%--------------------------------------------------------------------
%% Function: 
%% handle_call(Request, State) -> {ok, Reply, State} |
%%                                {swap_handler, Reply, Args1, State1, 
%%                                  Mod2, Args2} |
%%                                {remove_handler, Reply}
%% Description: Whenever an event manager receives a request sent using
%% gen_event:call/3,4, this function is called for the specified event 
%% handler to handle the request.
%%--------------------------------------------------------------------
handle_call(_Request, State) ->
    Reply = ok,
    {ok, Reply, State}.

%%--------------------------------------------------------------------
%% Function: 
%% handle_info(Info, State) -> {ok, State} |
%%                             {swap_handler, Args1, State1, Mod2, Args2} |
%%                              remove_handler
%% Description: This function is called for each installed event handler when
%% an event manager receives any other message than an event or a synchronous
%% request (or a system message).
%%--------------------------------------------------------------------
handle_info(_Info, State) ->
    {ok, State}.

%%--------------------------------------------------------------------
%% Function: terminate(Reason, State) -> void()
%% Description:Whenever an event handler is deleted from an event manager,
%% this function is called. It should be the opposite of Module:init/1 and 
%% do any necessary cleaning up. 
%%--------------------------------------------------------------------
terminate(_Reason, State)  when is_record(State, state_proc_cons) ->
    ok;
terminate(_Reason, State)  when is_record(State, state_dispatcher) ->
    ok;
terminate(_Reason, State)  when is_record(State, state_disk_log) ->
	disk_log:close(State#state_disk_log.name),
    ok.

%%--------------------------------------------------------------------
%% Function: code_change(OldVsn, State, Extra) -> {ok, NewState} 
%% Description: Convert process state when code is changed
%%--------------------------------------------------------------------
code_change(_OldVsn, State, _Extra) ->
    {ok, State}.

%%--------------------------------------------------------------------
%%% Internal functions
%%--------------------------------------------------------------------

pf_any(_)->
	true.

open_log(ArgL) ->
	case disk_log:open(ArgL) of
		{ok,Log} ->
			Log;
		{repaired, Log, {recovered, _Rec}, {badbytes, _Bad}} ->
			Log;
		{error, Reason} ->
			throw(Reason)
	end.